
# wxPython 选择源目录 目标目录 提交按钮 ,按拍摄日期年月分类存放照片。
# pyinstaller -F d:\pythonp\wx2\wx_readdirfile.py
# pyinstaller -F wx_readdirfile_mac_1_6.py
# pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pytz
# version 1.5 加入对子目录的处理

import wx
import os
import sys
from pathlib import Path
from shutil import copy
from datetime import datetime
import time

import exifread

from PIL import Image
from PIL.ExifTags import TAGS
import pillow_heif
from pillow_heif import register_heif_opener
import io
import exiftool
import pytz


# 创建register_heif_opener模块里的一个类然后再Image.open打开HEIC文件
register_heif_opener()

class MyFrameymapp(wx.Frame):
    def __init__(self, *args, **kw):
        super(MyFrameymapp, self).__init__(*args, **kw)

        self.InitUI()

    def InitUI(self):
        panel = wx.Panel(self)

        # 创建选择源目录的按钮和文本框
        src_label = wx.StaticText(panel, label="源目录:")
        self.src_path = wx.TextCtrl(panel)
        src_button = wx.Button(panel, label="选择源目录")
        src_button.Bind(wx.EVT_BUTTON, self.OnSelectSrc)

        # 创建选择目标目录的按钮和文本框
        dest_label = wx.StaticText(panel, label="目标目录:")
        self.dest_path = wx.TextCtrl(panel)
        dest_button = wx.Button(panel, label="选择目标目录")
        dest_button.Bind(wx.EVT_BUTTON, self.OnSelectDest)

        # 创建提交按钮
        submit_button = wx.Button(panel, label="提交")
        submit_button.Bind(wx.EVT_BUTTON, self.OnSubmit)

        # 创建提交按钮
        #submit_button = wx.Button(panel, label="提交")
        #submit_button.Bind(wx.EVT_BUTTON, self.OnSubmit)

        # 创建退出按钮
        exit_button = wx.Button(panel, label="退出")
        exit_button.Bind(wx.EVT_BUTTON, self.on_exit)

        # 创建进度条
        self.progress = wx.Gauge(panel, range=100, pos=(20, 150), size=(550, 25))

        # 布局
        sizer = wx.GridBagSizer(6, 6)
        sizer.Add(src_label, pos=(0, 0), flag=wx.LEFT|wx.TOP, border=10)
        sizer.Add(self.src_path, pos=(0, 1), span=(1, 3), flag=wx.EXPAND|wx.TOP|wx.RIGHT, border=10)
        sizer.Add(src_button, pos=(0, 4), flag=wx.TOP|wx.RIGHT, border=10)

        sizer.Add(dest_label, pos=(1, 0), flag=wx.LEFT|wx.TOP, border=10)
        sizer.Add(self.dest_path, pos=(1, 1), span=(1, 3), flag=wx.EXPAND|wx.TOP|wx.RIGHT, border=10)
        sizer.Add(dest_button, pos=(1, 4), flag=wx.TOP|wx.RIGHT, border=10)

        sizer.Add(submit_button, pos=(2, 0), span=(1, 2), flag=wx.ALIGN_CENTER|wx.TOP|wx.BOTTOM, border=10)

        sizer.Add(exit_button, pos=(2, 2), span=(1, 2), flag=wx.ALIGN_CENTER | wx.TOP | wx.BOTTOM, border=10)

        sizer.AddGrowableCol(1)
        panel.SetSizer(sizer)

        self.SetSize((600, 250))
        self.SetTitle('照片按拍摄日期年月分类存放程序。')
        self.Centre()

        self.Show()

    def OnSelectSrc(self, event):
        dlg = wx.DirDialog(self, "选择源目录", style=wx.DD_DEFAULT_STYLE)
        if dlg.ShowModal() == wx.ID_OK:
            self.src_path.SetValue(dlg.GetPath())
        dlg.Destroy()

    def OnSelectDest(self, event):
        dlg = wx.DirDialog(self, "选择目标目录", style=wx.DD_DEFAULT_STYLE)
        if dlg.ShowModal() == wx.ID_OK:
            self.dest_path.SetValue(dlg.GetPath())
        dlg.Destroy()

    def OnSubmit(self, event):
        # 处理业务部分
        #for i in range(101):
        #   time.sleep(0.05)  # 模拟长时间操作
        #   self.progress.SetValue(i)
        #   wx.Yield()  # 允许UI更新

        src = self.src_path.GetValue()
        dest = self.dest_path.GetValue()
        if os.path.isdir(src) and os.path.isdir(dest):
            #wx.MessageBox(f"源目录: {src}\n目标目录: {dest}", "提交信息", wx.OK | wx.ICON_INFORMATION)
            # 提交处理数据
            source_dir = src
            destination_dir = dest
            # 示例用法
            if self.check_directory_exists(source_dir):
                print(f"源文件目录: {source_dir}")
            else:
                print(f"### 源文件目录 {source_dir} 不存在！")
                sys.exit(1)  # 停止程序执行，并返回一个非零的退出状态码

            if self.check_directory_exists(destination_dir):
                print(f"目标文件目录: {destination_dir}")
            else:
                print(f"### 目标文件目录 {destination_dir} 不存在！")
                sys.exit(1)  # 停止程序执行，并返回一个非零的退出状态码

            # 处理分类存放程序
            # 使用示例
            # source_dir = 'Z:\Xiaomi 24069RA21C Camera Backup'  # 源目录路径
            # source_dir = 'D:\\redmi-ym\\2021-10'  # 源目录路径
            source_dir = source_dir
            # destination_dir = 'D:\\redmi-ym'  # 目标目录路径
            destination_dir = destination_dir
            if source_dir == destination_dir:
                print(f"#源目录和目的目录相同！")
                wx.MessageBox("源目录和目的目录相同", "错误", wx.OK | wx.ICON_ERROR)
                sys.exit(1)  # 停止程序执行，并返回一个非零的退出状态码

            #files = []
            # 获取当前时间并格式化
            current_time = time.strftime("%Y%m%d_%H%M%S")

            # 创建文件名
            logfile_name = f"log_{current_time}.txt"
            logfile_path = os.path.join(destination_dir, logfile_name)
            # 打开文件以写入模式
            logfile = open(logfile_path, 'w')

            total_files = 0
            total_filenum = 0
            for root, dirs, filenames in os.walk(source_dir):
                print('\n目录名：', root,"\n")

                # 写入文件
                logfile.write(f"\n目录名：{root}\n")

                # 统计目录下的文件数
                file_count = self.count_files_in_directory(root)
                for filename in filenames:
                    # files.append(os.path.join(root, filename))
                    #    total_files += 1
                    #    print(total_files, '.', os.path.join(root, filename))
                    if filename == ".DS_Store":
                        file_count =file_count  -1

                total_files = total_files + file_count
                # 设置进度条的最大值
                #progress_bar['value'] = 0
                #progress_bar['maximum'] = file_count
                #start_progress(progress_bar)
                # 动态设置进度条的 range
                new_range = file_count
                self.progress.SetRange(new_range)
                # 处理一个目录下的所有文件
                filenum = self.organize_files_by_date(root, destination_dir,logfile)
                total_filenum = total_filenum + filenum
                # 示例用法
                # file_count = count_files_in_directory(source_dir)

            print(f"源目录 {source_dir} 中的文件个数为: {total_files}")
            print(f"拷贝文件个数为: {total_filenum}")
            print('copy file ending...')
            # 写入文件
            logfile.write(f"源目录 {source_dir} 中的文件个数为: {total_files}\n")
            logfile.write(f"拷贝文件个数为: {total_filenum}\n")
            logfile.write('copy file ending...')
            # 确保文件被关闭
            logfile.close()

            wx.MessageBox("数据处理完成！", "信息", wx.OK | wx.ICON_INFORMATION)
        else:
            wx.MessageBox("请选择有效的目录", "错误", wx.OK | wx.ICON_ERROR)


    def organize_files_by_date(self,source_dir, destination_dir,logfile):
        # 创建目标目录，如果不存在的话
        Path(destination_dir).mkdir(parents=True, exist_ok=True)
        filenum = 0
        # 遍历源目录下的所有文件
        sorted_list = sorted(os.listdir(source_dir))
        for filename in sorted_list:
            if filename == ".DS_Store":
                continue

            file_path = os.path.join(source_dir, filename)

            # 如果是文件并且不在目标目录中，则按日期移动
            if os.path.isfile(file_path) and not os.path.exists(os.path.join(destination_dir, filename)):
                filenum = filenum + 1

                """
                模拟进度条增加的过程
                :param progress_bar: ttk.Progressbar对象
                """
                #update_progress_bar(progress_bar, 1)

                self.progress.SetValue(filenum)
                wx.Yield()  # 允许UI更新

                # 获取文件的创建时间
                creation_time = os.path.getmtime(file_path)
                file_datetime = datetime.fromtimestamp(creation_time)
                # 文件是.jpg
                if self.is_jpg_file(file_path):
                    # 打开照片文件，单独处理
                    with open(file_path, 'rb') as file:
                        # 使用exifread读取EXIF信息
                        tags = exifread.process_file(file)

                        # 尝试读取拍摄日期
                        try:
                            # 如果拍摄日期存在，它通常在EXIF格式的"DateTimeOriginal"标签中
                            date_taken = tags['EXIF DateTimeOriginal'].printable
                            file_datetime = datetime.strptime(date_taken, "%Y:%m:%d %H:%M:%S")
                            creation_time = int(file_datetime.timestamp())
                            # print(date_taken)
                            # print(f"Date Taken: {date_taken}")
                        except KeyError:
                            # 如果标签不存在，则打印错误消息
                            # print("Date Taken tag not found.")
                            date_taken = ''
                # 文件是.png
                if self.is_png_file(file_path):
                    # 打开照片文件，单独处理
                    with open(file_path, 'rb') as file:
                        # 使用exifread读取EXIF信息
                        tags = exifread.process_file(file)
                        if tags:
                            # 尝试读取拍摄日期
                            try:
                                # 如果拍摄日期存在，它通常在EXIF格式的"DateTimeOriginal"标签中
                                date_taken = tags['EXIF DateTimeOriginal'].printable
                                file_datetime = datetime.strptime(date_taken, "%Y:%m:%d %H:%M:%S")
                                creation_time = int(file_datetime.timestamp())
                                # print(date_taken)
                                # print(f"Date Taken: {date_taken}")
                            except KeyError:
                                # 如果标签不存在，则打印错误消息
                                # print("Date Taken tag not found.")
                                date_taken = ''
                # 文件是.raw
                if self.is_raw_file(file_path):
                    # 打开照片文件，单独处理

                    # with open(file_path, 'rb') as file:
                    #     # 使用exifread读取EXIF信息
                    #     tags = exifread.process_file(file)
                    #     if tags:
                    #         # 尝试读取拍摄日期
                    #         try:
                    #             # 如果拍摄日期存在，它通常在EXIF格式的"DateTimeOriginal"标签中
                    #             date_taken = tags['EXIF DateTimeOriginal'].printable
                    #             file_datetime = datetime.strptime(date_taken, "%Y:%m:%d %H:%M:%S")
                    #             creation_time = int(file_datetime.timestamp())
                    #             # print(date_taken)
                    #             # print(f"Date Taken: {date_taken}")
                    #         except KeyError:
                    #             # 如果标签不存在，则打印错误消息
                    #             # print("Date Taken tag not found.")
                    #             date_taken = ''
                    # 对cr2,c3,arw该用exiftool方式读取,取照片的拍摄日期
                    date_taken = self.get_exif_info(file_path)
                    if date_taken != '':
                        file_datetime = datetime.strptime(date_taken, "%Y:%m:%d %H:%M:%S")
                        creation_time = int(file_datetime.timestamp())
                    else:
                        date_taken = ''

                # 是 HEIC 文件
                if any([file_path.lower().endswith(ext) for ext in [".heic"]]):
                    datetime_original = self.get_datetime_original(file_path)
                    if datetime_original:
                        #print(file_path, "【时间】", datetime_original)
                        #print('Year:', datetime_original[0:4], 'Month:', datetime_original[5:7])
                        #year = datetime_original[0:4]
                        #month = year + "-" + datetime_original[5:7]
                        file_datetime = datetime.strptime(datetime_original, "%Y:%m:%d %H:%M:%S")
                        creation_time = int(file_datetime.timestamp())
                    else:
                        print('未找到拍摄日期和时间信息')
                # 是 mov mp4 文件
                if self.is_mov_file(file_path):
                    datetime_original = self.get_movfile_date( file_path)
                    if datetime_original != "err":
                        file_datetime = datetime.strptime(datetime_original, "%Y:%m:%d %H:%M:%S")
                        creation_time = int(file_datetime.timestamp())

                print(filenum, file_path, file_datetime)
                # 写入文件
                logfile.write(f"{filenum}, {file_path}, {file_datetime}\n")

                # 构建目标子目录的路径
                dest_subdir = os.path.join(destination_dir, f"{file_datetime.year}-{file_datetime.month}")

                # 创建子目录，如果不存在的话
                Path(dest_subdir).mkdir(parents=True, exist_ok=True)

                # 将文件复制到子目录
                copy(file_path, dest_subdir)

                # 修改文件的修改时间
                file_pathd = os.path.join(dest_subdir, filename)

                # filed = Path(file_pathd)
                # filed.touch(times=(creation_time, creation_time))

                os.utime(file_pathd, (creation_time, creation_time))

        return filenum

    def get_exif_info(self, file_path):
        try:
            # 取完整的 EXIF 信息
            #result = subprocess.check_output(["exiftool", file_path], universal_newlines=True)

            # 字段名替换字典
            field_replacements = {
                "Media Create Date": "\n媒体建立时间",
                "Date/Time Original": "\n拍摄时间",
                "File Modification Date/Time": "\n文件修改时间",
                "Lens ID": "镜头型号",
                "Lens Make": "镜头制造商",
                "Android Make": "手机制造商",
                "Make ": "设备制造商 ",
                "Camera Model Name": "设备型号",
                "Lens Model": "镜头型号",
                "Lens Type": "镜头类型"
            }
            # 替换提示为中文
            #for en_field, zh_field in field_replacements.items():
            #    result = result.replace(en_field, zh_field)

            # 创建 ExifTool 对象
            x1 = '1'  # 假设没有 GPS 信息
            with exiftool.ExifToolHelper() as et:
                metadata_list = et.get_metadata(file_path)
                if isinstance(metadata_list, list):
                    for metadata in metadata_list:
                        #print(metadata)
                        time_original = metadata.get("EXIF:DateTimeOriginal")
                        OffsetTime = metadata.get("EXIF:OffsetTime")
                        OffsetTimeOriginal = metadata.get("EXIF:OffsetTimeOriginal")
                        #FileModifyDate = metadata.get("File:FileModifyDate")
                        FileModifyDate = os.path.getmtime(file_path)
                        #FileModifyDate = datetime.fromtimestamp(FileModifyDate)

                        if time_original == None:
                            # 文件没有拍照日期，不修改gps信息
                            time_original = FileModifyDate
                            #print("==",time_original, OffsetTime, OffsetTimeOriginal, FileModifyDate)
                            return ''
                        else:
                            return time_original
        except subprocess.CalledProcessError as e:
            # 捕获异常并设置错误信息
            print(f"获取 EXIF 信息时出错: {e}")
            return '' #None
        except Exception as ex:
            # 捕获其他异常
            print(f"发生错误: {ex}")
            return '' #None

    def is_jpg_file(self,filename):
        #return filename.lower().endswith('.jpg')
        # 将文件名转换为小写，并检查是否以'.jpg'或'.jpeg'结尾
        return filename.lower().endswith(('.jpg', '.jpeg'))

    def is_png_file(self,filename):
        # 将文件名转换为小写，并检查是否以'.jpg'或'.jpeg'结尾
        #return filename.lower().endswith(('.jpg', '.jpeg'))
        return filename.lower().endswith('.png')

    def is_raw_file(self,filename):
        # 将文件名转换为小写，并检查是否以'.jpg'或'.jpeg'结尾
        return filename.lower().endswith(('.raw', '.cr', '.cr2', '.cr3', '.nef', '.arw', '.dng', '.orf', '.raf', '.rw2', '.pef', '.srw'))

    def is_mov_file(self,filename):
        #return filename.lower().endswith('.jpg')
        # 将文件名转换为小写，并检查是否以'.jpg'或'.jpeg'结尾
        return filename.lower().endswith(('.mov', '.mp4'))

    def count_files_in_directory(self,directory_path):
        try:
            # 获取目录中的所有文件和子目录
            entries = os.listdir(directory_path)
            # 过滤出文件（非子目录）
            files = [entry for entry in entries if os.path.isfile(os.path.join(directory_path, entry))]
            # 返回文件个数
            return len(files)
        except Exception as e:
            print(f"读取目录时发生错误: {e}")
            return 0

    def update_progress_bar(self,progress_bar, increment):
        """
        更新进度条的函数
        :param progress_bar: ttk.Progressbar对象
        :param increment: 进度条增加的值
        """
        current_value = progress_bar['value']
        max_value = progress_bar['maximum']
        new_value = current_value + increment
        if new_value > max_value:
            new_value = max_value
        progress_bar['value'] = new_value
        root.update_idletasks()  # 更新界面

    def start_progress(self,progress_bar):
        """
        模拟进度条增加的过程
        :param progress_bar: ttk.Progressbar对象
        """
        for i in range(101):
            self.update_progress_bar(progress_bar, 1)
            root.after(100)  # 暂停100毫秒

    def check_directory_exists(self,directory_path):
        """
        检查目录是否存在
        :param directory_path: 目录路径
        :return: 如果目录存在返回True，否则返回False
        """
        return os.path.isdir(directory_path)

    def on_exit(self, event):
        self.Close(True)

    # 获取HEIC格式文件"拍摄日期"的函数
    def get_datetime_original(self,image_path):
        image = Image.open(image_path)
        fstream = io.BytesIO(image.info["exif"][6:])
        exifdata = exifread.process_file(fstream, details=False)
        imageDateTime = str(exifdata.get("Image DateTime"))

        return imageDateTime

    def convert_utc_to_china_time(self,utc_time_str):
        # 解析UTC时间字符串
        #utc_time = datetime.strptime(utc_time_str, "%Y-%m-%dT%H:%M:%S%z")
        utc_time = datetime.strptime(utc_time_str, "%Y:%m:%d %H:%M:%S")

        # 创建UTC时区对象
        utc_timezone = pytz.timezone('UTC')

        # 将UTC时间转换为UTC时区对象
        utc_time = utc_timezone.localize(utc_time)

        # 创建中国时区对象
        china_timezone = pytz.timezone('Asia/Shanghai')

        # 将UTC时间转换为中国时间
        china_time = utc_time.astimezone(china_timezone)

        # 去掉时区信息，只保留时间部分
        china_time_str = china_time.strftime("%Y:%m:%d %H:%M:%S")

        return china_time_str

    # 通过exiftool获取mov mp4文件的建立日期
    def get_movfile_date(self,file_path):
        # 指定 ExifTool 可执行文件的路径
        # for windows
        #exiftool_path = r"C:\Program Files\ExifTool\exiftool.exe"

        # 创建 ExifTool 对象
        #with exiftool.ExifTool(executable=exiftool_path) as et:
        # 初始化ExifTool
        with exiftool.ExifToolHelper() as et:
            # 读取视频文件的元数据
            video_file = file_path
            metadata_list = et.get_metadata(video_file)
            # 检查返回值是否为列表
            if isinstance(metadata_list, list):
                for metadata in metadata_list:
                    file_modify_date = metadata.get('QuickTime:MediaCreateDate', '')
                    # 判断字符串前五个字符是否等于"0000:"
                    if file_modify_date[:5] == "0000:":
                        print(f"File: {os.path.basename(video_file)} - File Modify Date starts with '0000:'")
                        # 提取并处理File:FileModifyDate
                        file_modify_date = metadata.get('File:FileModifyDate', '')
                        if file_modify_date:
                            # 去掉最后的'+'号
                            # print(file_modify_date)
                            # file_modify_date = file_modify_date.rstrip('+08:00')
                            if self.contains_plus_sign(file_modify_date):
                                # 有 + 号
                                file_modify_date = file_modify_date[0:19]
                                #print( file_modify_date)
                                file_modify_date = self.convert_utc_to_china_time(file_modify_date)
                                dt = datetime.strptime(file_modify_date, "%Y:%m:%d %H:%M:%S")

                                rdate = dt.strftime('%Y:%m:%d %H:%M:%S')

                            else:
                                dt = datetime.strptime(file_modify_date, "%Y:%m:%d %H:%M:%S")
                                rdate = dt.strftime('%Y:%m:%d %H:%M:%S')
                    else:
                        # 字符串转日期，指定时区
                        try:
                            # 解析日期时间字符串
                            print(file_modify_date)

                            #dt = datetime.strptime(file_modify_date, "%Y:%m:%d %H:%M:%S")
                            # 指定时区
                            #tz = pytz.timezone('Asia/Shanghai')
                            #dt = dt.astimezone(tz)
                            dt = self.convert_utc_to_china_time(file_modify_date)
                            print(dt)
                            rdate = dt
                            #print(f"File: {os.path.basename(video_file)} - Processed File Modify Date: {dt.strftime('%Y-%m-%d %H:%M:%S %Z%z')}")
                        except ValueError:
                            print(f"File: {os.path.basename(video_file)} - Invalid date format: {file_modify_date}")
                            return "err"
                    # 打印处理后的日期
                    #print(f"File: {os.path.basename(video_file)} - Processed File Modify Date: {file_modify_date}")

                    return rdate

    def contains_plus_sign(self,date_string):
        return '+' in date_string

if __name__ == '__main__':
    app = wx.App(False)
    # None)
    frame = MyFrameymapp(None)
    #frame.Show(True)
    app.MainLoop()

